import collections
import json
from itertools import groupby
from operator import itemgetter

import numpy as np

Group = collections.namedtuple('group', ['gid', 'servers'])
Shared = collections.namedtuple('shared', ['shared_id', 'gid'])
servers = [{'id': 'S1', 'url': '10.10.10.10:8001', 'node_state': 'Leader'},
           {'id': 'S2', 'url': '10.10.10.10:8002', 'node_state': 'Follower'},
           {'id': 'S3', 'url': '10.10.10.10:8003', 'node_state': 'Follower'}]


def get_new_config(num=0, shared=None, groups=None):
    if groups is None:
        groups = {}
    if shared is None:
        shared = {}
    return {
        'num': num,
        'Shared': shared,
        'Groups': groups
    }


class SharedMaster:
    def __init__(self):
        self.configs = [get_new_config()]

    def append_config(self, config):
        self.configs.append(config)

    def shared_init(self):
        """切片且分配Group,初始Group为空，所有Shared的group_id设置为0"""
        # TODO the rule of sharing
        shared = self.configs[-1]['Shared']
        for i in range(1, 11):
            shared[i] = 0

    def shared_save(self):
        """保存最新一份config"""
        path = ''
        config = self.configs[-1]
        config_name = path + f"config_{config['num']}.json"

        with open(config_name, 'w') as f:
            f.write(json.dumps(config))


def get_shared_count(shared_len, group_len):
    """获得此时切片应拥有的最少数量和最大数量"""
    share_count = shared_len / group_len
    shared_min = int(np.floor(share_count))
    shared_max = int(np.ceil(share_count))
    return shared_min, shared_max


def balance_shared_to_group(shared_dict, gid_list):
    """平衡每个人Group上面的Shared数量"""

    shared_min, shared_max = get_shared_count(len(shared_dict), len(gid_list))
    flag = True
    while flag:
        group_own_shared = {}
        for gid in gid_list:
            group_own_shared.setdefault(gid, [])
        for key, value in shared_dict.items():
            group_own_shared.setdefault(value, []).append(key)
        group_own = sorted(group_own_shared.items(), key=lambda x: len(x[1]), reverse=True)
        max_count_gid = group_own[0][0]
        min_count_gid, min_count = group_own[-1][0], len(group_own[-1][1])
        if min_count_gid == 0:
            min_count_gid, min_count = group_own[-2][0], len(group_own[-2][1])
        if 0 in group_own_shared.keys():
            move_shared_id = group_own_shared[0][-1]
            shared_dict[move_shared_id] = min_count_gid
            continue
        else:
            move_shared_id = group_own_shared[max_count_gid][-1]
            if min_count >= shared_min:
                flag = False
            else:
                shared_dict[move_shared_id] = min_count_gid

    return shared_dict


def join(sm, server_group):
    length = len(sm.configs)
    last_config = sm.configs[-1]
    new_groups = last_config['Groups'].copy()
    for gid, servers in server_group:
        new_groups[gid] = servers
    shared_dict = balance_shared_to_group(last_config['Shared'].copy(), list(new_groups.keys()))
    new_config = get_new_config(length, shared_dict, new_groups)
    sm.configs.append(new_config)


def leave(sm, gid_list):
    """将group删除"""
    length = len(sm.configs)
    last_config = sm.configs[-1]
    new_groups = last_config['Groups'].copy()
    shared = last_config['Shared'].copy()
    group_own_shared = {}
    for key, value in shared.items():
        group_own_shared.setdefault(value, []).append(key)
    for gid in gid_list:
        new_groups.pop(gid)
        for shared_id in group_own_shared[gid]:
            shared[shared_id] = 0
    shared_dict = balance_shared_to_group(shared, list(new_groups.keys()))
    new_config = get_new_config(length, shared_dict, new_groups)
    sm.configs.append(new_config)


def move(sm, shared_ids, gid):
    """将指定切片移动到某一个group组中去"""
    length = len(sm.configs)
    last_config = sm.configs[-1]
    new_config = get_new_config(length, last_config['Shared'].copy(), last_config['Groups'].copy())
    for shared_id in shared_ids:
        new_config['Shared'][shared_id] = gid
    sm.append_config(new_config)


def query(sm, config_num=-1):
    return sm.configs[config_num]


if __name__ == '__main__':
    print("Shared_Test")
    sm = SharedMaster()
    sm.shared_init()
    print(sm.configs[-1]['Shared'])
    server_group = [Group(1, servers)]
    join(sm, server_group)
    print(sm.configs[-1]['Shared'])
    server_group = [Group(2, servers)]
    join(sm, server_group)
    print(sm.configs[-1]['Shared'])
    server_group = [Group(5, servers)]
    join(sm, server_group)
    print(sm.configs[-1]['Shared'])
    server_group = [Group(7, servers)]
    join(sm, server_group)
    server_group = [Group(4, servers)]
    join(sm, server_group)
    server_group = [Group(6, servers)]
    join(sm, server_group)
    move(sm, [1, 2], 1)
    move(sm, [3], 5)
    move(sm, [4, 5], 2)
    move(sm, [6, 7], 7)
    move(sm, [8], 4)
    move(sm, [9, 10], 6)
    print(sm.configs[-1]['num'])
    print(sm.configs[-1]['Shared'])
    leave(sm, [1])
    print(sm.configs[-1]['Shared'])
    sm.shared_save()